package com.yanyeori.framework.core.util.excel;

import com.yanyeori.framework.core.constant.BaseConstant;
import com.yanyeori.framework.core.exception.BusinessException;
import com.yanyeori.framework.core.util.HttpUtil;
import com.yanyeori.framework.core.util.StringUtil;
import org.apache.poi.hssf.usermodel.HSSFRichTextString;
import org.apache.poi.hssf.usermodel.HSSFWorkbook;
import org.apache.poi.ss.usermodel.*;
import org.apache.poi.xssf.usermodel.XSSFRichTextString;
import org.apache.poi.xssf.usermodel.XSSFWorkbook;

import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.OutputStream;
import java.lang.reflect.Field;
import java.util.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * Excel写工具
 *
 * @author chenkuan 2019/11/11
 */
public class ExcelWriter extends ExcelUtil {

    private final ExcelConfig config;

    private final Workbook workbook;

    private ExcelWriter(ExcelConfig excelConfig, Workbook workbook) {
        this.config = excelConfig;
        this.workbook = workbook;
    }

    public static ExcelWriter.ExcelConfig builder() {
        return new ExcelWriter.ExcelConfig();
    }

    /**
     * 导出无头部标题行Excel
     *
     * @param renders  表格标题对应的字段集合
     * @param dataList 数据集合
     */
    public <T> ExcelWriter export(List<String> renders, Collection<T> dataList) {
        return export(renders, dataList, null);
    }

    /**
     * 导出无头部标题行Excel
     *
     * @param renders   表格标题对应的字段集合
     * @param dataList  数据集合
     * @param sheetName sheet名称
     */
    public <T> ExcelWriter export(List<String> renders, Collection<T> dataList, String sheetName) {
        exportExcel(null, renders, dataList, sheetName);
        return this;
    }

    /**
     * 导出带有头部标题行的Excel
     *
     * @param headers  头部标题集合
     * @param renders  表格标题对应的字段集合
     * @param dataList 数据集合
     */
    public <T> ExcelWriter export(List<String> headers, List<String> renders, Collection<T> dataList) {
        return export(headers, renders, dataList, null);
    }

    /**
     * 导出带有头部标题行的Excel
     *
     * @param headers   头部标题集合
     * @param renders   表格标题对应的字段集合
     * @param dataList  数据集合
     * @param sheetName sheet名称
     */
    public <T> ExcelWriter export(List<String> headers, List<String> renders, Collection<T> dataList, String sheetName) {
        exportExcel(headers, renders, dataList, sheetName);
        return this;
    }

    /**
     * 根据字段注解@Excel导出Excel
     *
     * @param dataList 数据集合
     */
    public <T> ExcelWriter export(Collection<T> dataList) {
        return export(dataList, null);
    }

    /**
     * 根据字段注解@Excel导出Excel
     *
     * @param dataList  数据集合
     * @param sheetName sheet名称
     */
    public <T> ExcelWriter export(Collection<T> dataList, String sheetName) {
        //头部标题集合
        List<String> headers = new ArrayList<>();
        //表格标题对应的字段集合
        List<String> renders = new ArrayList<>();
        //获取所有Excel注解字段
        Field[] fields = dataList.iterator().next().getClass().getDeclaredFields();
        List<Object[]> fieldExcels = new ArrayList<>();
        for (Field field : fields) {
            field.setAccessible(true);
            Excel excel = field.getAnnotation(Excel.class);
            if (excel != null) {
                fieldExcels.add(new Object[]{field, excel});
            }
        }
        if (fieldExcels.isEmpty()) {
            throw new BusinessException("Fields that do not have Excel annotations on the object cannot be exported");
        }
        //根据sort排序
        fieldExcels.sort(Comparator.comparing(objects -> ((Excel) objects[1]).sort()));
        boolean dateFormatMapEmpty = config.dateFormatMap.isEmpty();
        fieldExcels.forEach(objects -> {
            Field field = (Field) objects[0];
            Excel excel = (Excel) objects[1];
            //处理headers
            headers.add(excel.name());
            //处理renders
            renders.add(field.getName());
            //处理日期格式
            if (dateFormatMapEmpty) {
                config.dateFormatMap.put(field.getName(), excel.dateFormat());
            }
        });
        if (headers.stream().allMatch(StringUtil::isEmpty)) {
            headers.clear();
        }
        //导出Excel
        exportExcel(headers, renders, dataList, sheetName);
        return this;
    }

    /**
     * 将Excel表写入文件输出流
     *
     * @param out 输出流
     */
    public void write(OutputStream out) throws IOException {
        workbook.write(out);
    }

    /**
     * 将Excel表写入Response下载文件流
     *
     * @param fileName 文件名
     * @param response 响应
     */
    public void write(String fileName, HttpServletResponse response) throws IOException {
        String fileType = Version.EXCEL_2003 == config.version ? ".xls" : ".xlsx";
        response.setContentType("application/vnd.ms-excel");
        response.addHeader("Content-Disposition", "attachment;filename=" + HttpUtil.encodeUrl(fileName) + fileType);
        workbook.write(response.getOutputStream());
    }

    private <T> void exportExcel(List<String> headers, List<String> renders, Collection<T> dataList, String sheetName) {
        try {
            if (Version.EXCEL_2003 == config.version) {
                exportExcel2003(headers, renders, dataList, sheetName);
            } else {
                exportExcel2007(headers, renders, dataList, sheetName);
            }
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    /**
     * 通用Excel导出方法,利用反射机制遍历对象的所有字段，将数据写入Excel文件中
     * 此版本生成2007以上版本的文件 (文件后缀：xlsx)
     *
     * @param headers   表格头部标题集合
     * @param renders   表格标题对应的字段集合
     * @param dataList  需要显示的数据集合,集合中一定要放置符合JavaBean风格的类的对象。此方法支持的JavaBean属性的数据类型有基本数据类型及String,Date
     * @param sheetName sheet名称
     */
    private <T> void exportExcel2007(List<String> headers, List<String> renders, Collection<T> dataList, String sheetName) throws NoSuchFieldException, IllegalAccessException, IOException {
        // 生成一个表格
        Sheet sheet = sheetName != null ? workbook.createSheet(sheetName) : workbook.createSheet();
        // 设置表格默认列宽行高
        sheet.setDefaultColumnWidth(20);
        // 生成标题样式
        CellStyle style = workbook.createCellStyle();
        // 设置标题样式
        style.setFillPattern(FillPatternType.SOLID_FOREGROUND);
        // 水平垂直居中
        style.setAlignment(HorizontalAlignment.CENTER);
        style.setVerticalAlignment(VerticalAlignment.CENTER);
        //单元格颜色
        style.setFillForegroundColor(IndexedColors.WHITE.getIndex());
        // 设置标题字体
        //XSSFFont font = workbook.createFont();
        //font.setFontName("宋体");
        //font.setColor(IndexedColors.WHITE.index);
        //font.setFontHeightInPoints((short) 11);
        //style.setFont(font);
        //记录行数
        int index = 0;
        // 产生表格标题行
        if (headers != null && headers.size() > 0) {
            Row row = sheet.createRow(index);
            Cell cell;
            for (int i = 0, headerSize = headers.size(); i < headerSize; i++) {
                cell = row.createCell(i);
                cell.setCellStyle(getStyleBorder(style, false, i == headerSize - 1));
                cell.setCellValue(new XSSFRichTextString(headers.get(i)));
            }
            index++;
        }
        // 遍历集合数据，产生数据行
        Iterator<T> it = dataList.iterator();
        T t;
        Field field;
        XSSFRichTextString richString;
        Pattern p = Pattern.compile("^//d+(//.//d+)?$");
        Matcher matcher;
        Row row;
        Cell cell;
        Object value;
        String textValue;
        while (it.hasNext()) {
            row = sheet.createRow(index);
            t = it.next();
            for (int i = 0, renderSize = renders.size(); i < renderSize; i++) {
                cell = row.createCell(i);
                cell.setCellStyle(getStyleBorder(style, !it.hasNext(), i == renderSize - 1));
                String fieldName = renders.get(i);
                field = t.getClass().getDeclaredField(fieldName);
                field.setAccessible(true);
                value = field.get(t);
                // 判断值的类型后进行强制类型转换
                textValue = formatStringText(value, config.dateFormatMap.getOrDefault(fieldName, BaseConstant.FORMAT_DATETIME));
                cell.setCellValue(textValue);
                if (textValue != null) {
                    matcher = p.matcher(textValue);
                    if (matcher.matches()) {
                        cell.setCellValue(Double.parseDouble(textValue));
                    } else {
                        richString = new XSSFRichTextString(textValue);
                        cell.setCellValue(richString);
                    }
                }
            }
            index++;
        }
    }

    /**
     * 通用Excel导出方法,利用反射机制遍历对象的所有字段，将数据写入Excel文件中
     * 此方法生成2003版本的excel,文件名后缀：xls
     *
     * @param headers   表格头部标题集合
     * @param renders   表格标题对应的字段集合
     * @param dataList  需要显示的数据集合,集合中一定要放置符合JavaBean风格的类的对象。此方法支持的JavaBean属性的数据类型有基本数据类型及String,Date
     * @param sheetName sheet名称
     */
    private <T> void exportExcel2003(List<String> headers, List<String> renders, Collection<T> dataList, String sheetName) throws NoSuchFieldException, IllegalAccessException, IOException {
        // 生成一个表格
        Sheet sheet = sheetName != null ? workbook.createSheet(sheetName) : workbook.createSheet();
        // 设置表格默认列宽行高
        sheet.setDefaultColumnWidth(20);
        // 生成标题样式
        CellStyle style = workbook.createCellStyle();
        // 设置标题样式
        style.setFillPattern(FillPatternType.SOLID_FOREGROUND);
        // 水平垂直居中
        style.setAlignment(HorizontalAlignment.CENTER);
        style.setVerticalAlignment(VerticalAlignment.CENTER);
        //单元格颜色
        style.setFillForegroundColor(IndexedColors.WHITE.getIndex());
        // 设置标题字体
        //HSSFFont font = workbook.createFont();
        //font.setFontName("宋体");
        //font.setColor(IndexedColors.WHITE.index);
        //font.setFontHeightInPoints((short) 11);
        //style.setFont(font);
        //记录行数
        int index = 0;
        // 产生表格标题行
        if (headers != null && headers.size() > 0) {
            Row row = sheet.createRow(index);
            Cell cell;
            for (int i = 0, headerSize = headers.size(); i < headerSize; i++) {
                cell = row.createCell(i);
                cell.setCellStyle(getStyleBorder(style, false, i == headerSize - 1));
                cell.setCellValue(new HSSFRichTextString(headers.get(i)));
            }
            index++;
        }
        // 遍历集合数据，产生数据行
        Iterator<T> it = dataList.iterator();
        T t;
        Field field;
        HSSFRichTextString richString;
        Pattern p = Pattern.compile("^//d+(//.//d+)?$");
        Matcher matcher;
        Row row;
        Cell cell;
        Object value;
        String textValue;
        while (it.hasNext()) {
            row = sheet.createRow(index);
            t = it.next();
            for (int i = 0, renderSize = renders.size(); i < renderSize; i++) {
                cell = row.createCell(i);
                cell.setCellStyle(getStyleBorder(style, !it.hasNext(), i == renderSize - 1));
                String fieldName = renders.get(i);
                field = t.getClass().getDeclaredField(fieldName);
                field.setAccessible(true);
                value = field.get(t);
                // 判断值的类型后进行强制类型转换
                textValue = formatStringText(value, config.dateFormatMap.getOrDefault(fieldName, BaseConstant.FORMAT_DATETIME));
                cell.setCellValue(textValue);
                if (textValue != null) {
                    matcher = p.matcher(textValue);
                    if (matcher.matches()) {
                        cell.setCellValue(Double.parseDouble(textValue));
                    } else {
                        richString = new HSSFRichTextString(textValue);
                        cell.setCellValue(richString);
                    }
                }
            }
            index++;
        }
    }

    /**
     * 配置类
     */
    public static class ExcelConfig {
        /**
         * 导出的excel版本
         */
        private Version version = Version.EXCEL_2007;

        /**
         * 字段对应日期格式
         */
        private Map<String, String> dateFormatMap = new HashMap<>();

        public ExcelWriter build() {
            return new ExcelWriter(this, Version.EXCEL_2003 == version ? new HSSFWorkbook() : new XSSFWorkbook());
        }

        public ExcelConfig setVersion(Version version) {
            this.version = version;
            return this;
        }

        public ExcelConfig setDateFormat(String field, String format) {
            this.dateFormatMap.put(field, format);
            return this;
        }
    }

    public enum Version {
        EXCEL_2007,
        EXCEL_2003
    }
}
